package com.darren.framework.dao.impl;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Repository;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import com.darren.framework.dao.GenericDao;
import com.darren.framework.entity.PageBean;

@Repository(value="genericDao")
public abstract class GenericDaoImpl<T,PK extends Serializable> extends BaseDaoImpl implements GenericDao<T, PK> {
	
	protected Logger logger = LogManager.getLogger(getClass());
	
	@Autowired
	protected HibernateTemplate template;
	
	@Autowired
    protected SessionFactory sessionFactory;
	
	private Class<T> clazz;
	
	protected Map<String,Field> fieldMap;
	
	@SuppressWarnings("unchecked")
	public GenericDaoImpl(){
		clazz = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
		fieldMap = getNameAndType(clazz);
	}
	
	private Map<String,Field> getNameAndType(Class<?> clazz){
		Map<String,Field> fieldMap = new HashMap<String,Field>();
		Field[] fields = clazz.getDeclaredFields();
		for(Field field:fields){
			Annotation[] annotations = field.getDeclaredAnnotations();
			for(Annotation annotation:annotations){
				if("javax.persistence.Column".equals(annotation.annotationType().getName())){	
					Class<?> clz = field.getClass();// .getGenericType().getClass();
					Map<String,Field> childFiels = getNameAndType(clz);
					for (String key : childFiels.keySet()) { 
						fieldMap.put(field.getName()+"."+key, childFiels.get(key));
					}
					if(childFiels.size() == 0){
						fieldMap.put(field.getName(), field);
					}
					break;
				}else if("javax.persistence.Transient".equals(annotation.annotationType().getName())){					
					break;
				}
			}
		}
		return fieldMap;
	}
	
	@Override
	public Map<String,Object> constructHQLQuery(JSONObject queryJson){
		Map<String,Object> queryMap = new HashMap<String,Object>();
		List<Object> parameters = new ArrayList<Object>();
		String likes = "";
		String others = "";
		for (Map.Entry<String, Object> queryEntry : queryJson.entrySet()) {
            String queryKey = queryEntry.getKey();
            if("likes".equals(queryKey)){
            	JSONObject likesJson = queryJson.getJSONObject(queryKey);
            	for (Map.Entry<String, Object> likesEntry : likesJson.entrySet()){
            		String likesKey = likesEntry.getKey();
            		String value = likesJson.getString(likesKey);
            		if(value != null && !"".equals(value.trim())){
            			if(fieldMap.containsKey(likesKey)){
                			likes +=  " or " + likesKey + " like ? ";
                			parameters.add("%"+value+"%");
                		}
            		}
            	}
            	if(!"".equals(likes)){
            		likes = "(" + likes.substring(3) + ")";
            	}            	
            }else{
            	String operator = "";
            	if("like".equals(queryKey)){
            		operator = " like ";
            	}else if("equals".equals(queryKey)){
            		operator = " = ";
            	}else if("greaterThan".equals(queryKey)){
            		operator = " > ";
            	}else if("greaterThanOrEquals".equals(queryKey)){
            		operator = " >= ";
            	}else if("lessThan".equals(queryKey)){
            		operator = " < ";
            	}else if("lessThanOrEquals".equals(queryKey)){
            		operator = " <= ";
            	}
            	if(!"".equals(operator)){
            		JSONObject otherJson = queryJson.getJSONObject(queryKey);
                	for (Map.Entry<String, Object> otherEntry : otherJson.entrySet()){
                		String otherKey = otherEntry.getKey();
                		String value = otherJson.getString(otherKey);
                		if(value != null && !"".equals(value.trim())){
                			if(fieldMap.containsKey(otherKey)){
                    			others = " and " + otherKey + operator + " ? ";
                    			Field field = fieldMap.get(otherKey);
                    			if("like".equals(operator)){
                    				parameters.add("%"+value+"%");
                    			}else{
                    				parameters.add(TypeUtils.castToJavaBean(value, field.getType()));
                    			}
                    		}
                		}
                	}
            	}
            }
        }
		queryMap.put("hql", others + " and " + likes);
		queryMap.put("parameters", parameters);
		return queryMap;
	}
	
	@Override
	public T save(T entity) {
		Session session = sessionFactory.getCurrentSession();
		session.save(entity);
		session.flush();
		return entity;
	}
	
	@Override
	public T merge(T entity) {
		Session session = sessionFactory.getCurrentSession();
		session.merge(entity);
		session.flush();
		return entity;
	}
	
	@Override
	public T findById(PK id) {
		Session session = sessionFactory.getCurrentSession();
		return (T)session.get(clazz, id);
	}


	@Override
	public T update(T entity) {
		Session session = sessionFactory.getCurrentSession();
		session.update(entity);
		session.flush();
		return entity;
	}

	@Override
	public T delete(T entity) {
		Session session = sessionFactory.getCurrentSession();
		session.delete(entity);
		return entity;
	}
	
	@Override
	public void deleteById(PK id) {
		T entity = findById(id);
		if(entity != null){
			delete(entity);
		}
	}
	
	@Override
	public List<T> getAll(){
		Session session = sessionFactory.getCurrentSession();
		Query<T> query  = session.createQuery(" from " + clazz.getSimpleName(),clazz);
		return query.list();
	}

	@Override
	public PageBean queryForPageBySQL(String sql, Integer pageSize, Integer currentPage,Object... objs) {
		Session session = sessionFactory.getCurrentSession();		
		//Query query = session.createSQLQuery(sql).addEntity(clazz);
		NativeQuery<T> query = session.createNativeQuery(sql, clazz);
		for(int i=0;i<objs.length;i++){
			query.setParameter(i, objs[i]);
		}
		
		//得到滚动结果集
		ScrollableResults scroll = query.scroll();
		
		//滚动到最后一行
		scroll.last();
		int count = scroll.getRowNumber() + 1;
		
		//设置分页位置
		if(count >= currentPage * pageSize){			
			query.setFirstResult((currentPage - 1 ) * pageSize);
		}else{
			currentPage = count/pageSize + 1;
			query.setFirstResult(count - count % pageSize);
		}		
		query.setMaxResults(pageSize);		
		PageBean pageBean = new PageBean();
		pageBean.setPageSize(pageSize);
		pageBean.setCurrentPage(currentPage);
		pageBean.setTotalRow(count);
		pageBean.setList(query.list());
		pageBean.init();
		return pageBean;
	}

	@Override
	public List<T> queryByCondition(String hql,Object... objs) {
		Session session = sessionFactory.getCurrentSession();		
//		Query query = session.createQuery(hql);
		Query<T> query = session.createQuery(hql, clazz);
		for(int i=0;i<objs.length;i++){
			query.setParameter(i, objs[i]);
		}
		return query.list();
	}
	
	@Override
	public PageBean queryForPageByHQL(String hql, Integer pageSize, Integer currentPage,Object... objs) {
		Session session = sessionFactory.getCurrentSession();		
		Query<T> query = session.createQuery(hql, clazz);
		for(int i=0;i<objs.length;i++){
			query.setParameter(i, objs[i]);
		}
		
		//得到滚动结果集
		ScrollableResults scroll = query.scroll();
		
		//滚动到最后一行
		scroll.last();
		int count = scroll.getRowNumber() + 1;
		
		//设置分页位置
		if(count >= currentPage * pageSize){			
			query.setFirstResult((currentPage - 1 ) * pageSize);
		}else{
			currentPage = count/pageSize + 1;
			query.setFirstResult(count - count % pageSize);
		}
		query.setMaxResults(pageSize);		
		PageBean pageBean = new PageBean();
		pageBean.setPageSize(pageSize);
		pageBean.setCurrentPage(currentPage);
		pageBean.setTotalRow(count);
		pageBean.setList(query.list());
		pageBean.init();
		return pageBean;
	}
}